package com.googlecode.owltoskos;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

import com.hp.hpl.jena.graph.compose.Intersection;
import com.hp.hpl.jena.ontology.AnnotationProperty;
import com.hp.hpl.jena.ontology.DatatypeProperty;
import com.hp.hpl.jena.ontology.HasValueRestriction;
import com.hp.hpl.jena.ontology.Individual;
import com.hp.hpl.jena.ontology.IntersectionClass;
import com.hp.hpl.jena.ontology.ObjectProperty;
import com.hp.hpl.jena.ontology.OntClass;
import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.ontology.OntModelSpec;
import com.hp.hpl.jena.ontology.OntProperty;
import com.hp.hpl.jena.ontology.Ontology;
import com.hp.hpl.jena.ontology.Restriction;
import com.hp.hpl.jena.ontology.SomeValuesFromRestriction;
import com.hp.hpl.jena.ontology.UnionClass;
import com.hp.hpl.jena.rdf.model.Literal;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.Property;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.rdf.model.Statement;
import com.hp.hpl.jena.rdf.model.StmtIterator;
import com.hp.hpl.jena.util.iterator.ExtendedIterator;
import com.hp.hpl.jena.vocabulary.RDFS;

/**
 * 
 */
public class OwlToSkosConverter {

    private OntModel skosModel = ModelFactory.createOntologyModel(OntModelSpec.OWL_DL_MEM);

    private OntModel owlModel = ModelFactory.createOntologyModel(OntModelSpec.OWL_DL_MEM);
    
    private String defaultURI = "http://www.example.com";

    private Ontology skosOntology;
    
    private Ontology owlOntology;
    
    public static String SKOS_IMPORT = "http://www.w3.org/2009/08/skos-reference/skos-owl1-dl.rdf";
    public static String SKOS = "http://www.w3.org/2004/02/skos/core#";

    private OntClass skosConcept;

    private ObjectProperty skosNarrowerProperty;
    
    private ObjectProperty skosBroaderProperty;
    
    private ObjectProperty skosExactMatchProperty;

    private AnnotationProperty skosPrefLabelProperty;

    private AnnotationProperty skosDefinitionProperty;
    
    public OwlToSkosConverter() {
        initialize();
    }
    
    private void initialize() {
        skosOntology = skosModel.createOntology(defaultURI);
        Resource skosOnt = skosModel.createResource(SKOS_IMPORT);
        skosOntology.addImport(skosOnt);
        skosModel.loadImports();
        
        skosConcept = skosModel.getOntClass(SKOS+"Concept");
        skosNarrowerProperty = skosModel.getObjectProperty(SKOS+"narrower");
        skosBroaderProperty = skosModel.getObjectProperty(SKOS+"broader");
        skosExactMatchProperty = skosModel.getObjectProperty(SKOS+"exactMatch");
        skosPrefLabelProperty = skosModel.getAnnotationProperty(SKOS+"prefLabel");
        skosDefinitionProperty = skosModel.getAnnotationProperty(SKOS+"definition");
        
        owlOntology = owlModel.createOntology(defaultURI);
    }
        
    public OntModel getSkosModel() {
        return skosModel;
    }

    public OntModel getOWLModel() {
        return owlModel;
    }
    
    public void loadOntology(String uri) {
        Resource ont = owlModel.createResource(uri);
        owlOntology.addImport(ont);
        owlModel.loadImports();        
    }
    
    public void loadOntology(File file) {
        loadOntology(file.toURI().toString());
    }

    public void convert() {
        createConcepts();
        copyIndividuals();
    }
    
    private void copyIndividuals() {
        ExtendedIterator<Individual> it = owlModel.listIndividuals();
        while (it.hasNext()) {
            Individual i = it.next();
            Individual skosI = i.inModel(skosModel).as(Individual.class);
            
        }
    }

    private void createConcepts() {
        ExtendedIterator<OntClass> it = owlModel.listClasses();
        while (it.hasNext()) {
            OntClass owlClass = it.next();
            if (owlClass.getURI() == null || owlClass.isRestriction()) continue;
            Individual concept = getOrCreateConcept(owlClass.getURI());
            copyLabels(owlClass,concept);
            copyComments(owlClass,concept);
            copyVersionInfo(owlClass,concept);
            copyProperties(owlClass,concept);
            convertSuperClasses(owlClass,concept);
            convertEquivalentClasses(owlClass,concept);
        }
    }
    
    private void copyProperties(OntClass owlClass, Individual concept) {
        StmtIterator it = owlClass.listProperties();
        while (it.hasNext()) {
            Statement statement = it.next();
            Property prop = statement.getPredicate();
            if (prop.canAs(AnnotationProperty.class)) {
                AnnotationProperty annotation = skosModel.createAnnotationProperty(prop.getURI());
                try {
                    concept.addProperty(annotation, statement.getLiteral());
                } catch (Exception e) {
                }
            } else if (prop.canAs(DatatypeProperty.class)) {
                DatatypeProperty dtp = skosModel.createDatatypeProperty(prop.getURI());
                try {
                    concept.addProperty(dtp, statement.getLiteral());
                } catch (Exception e) {
                }
            } else if (prop.canAs(ObjectProperty.class)) {
                ObjectProperty op = skosModel.createObjectProperty(prop.getURI());
                concept.addProperty(op, statement.getObject().inModel(skosModel));
            } 
        }
    }

    private void convertEquivalentClasses(OntClass owlClass, Individual concept) {
        ExtendedIterator<OntClass> it = owlClass.listEquivalentClasses();
        while (it.hasNext()) {
            OntClass cls = it.next();
            if (cls.isIntersectionClass()) {
                handleIntersectionClass(owlClass, cls.asIntersectionClass(), concept);
            } else if (cls.isUnionClass()) {
                handleUnionClass(owlClass, cls.asUnionClass(), concept);
            } else if (cls.getURI() == null) {
                continue;
            } else {
                Individual i = getOrCreateConcept(cls.getURI());
                concept.addProperty(skosExactMatchProperty, i);
            }
        }
    }

    private void handleUnionClass(OntClass owlClass, UnionClass union,
            Individual concept) {
        ExtendedIterator<? extends OntClass> it = union.listOperands();
        while (it.hasNext()) {
            OntClass operand = it.next();
            if (operand.getURI() == null) continue;
            Individual i = getOrCreateConcept(operand.getURI());
            concept.addProperty(skosNarrowerProperty, i);
        }
    }

    private void handleIntersectionClass(OntClass owlClass, IntersectionClass intersect,
            Individual concept) {
        ExtendedIterator<? extends OntClass> it = intersect.listOperands();
        while (it.hasNext()) {
            OntClass operand = it.next();
            if (operand.getURI() == null) continue;
            Individual i = getOrCreateConcept(operand.getURI());
            concept.addProperty(skosBroaderProperty, i);
        }
    }

    private void handleSomeValuesFrom(OntClass owlClass, SomeValuesFromRestriction someValuesFrom,
            Individual concept) {
        OntProperty prop = someValuesFrom.getOnProperty();
        Resource c = someValuesFrom.getSomeValuesFrom();
        if (c.canAs(OntClass.class)) {
            copyProperty(prop,concept.getOntModel());
            concept.addProperty(prop.inModel(concept.getOntModel()).as(Property.class), c);
        }
    }
    
    private void copyProperty(OntProperty prop, OntModel model) {
        if (!model.contains(prop, (Property)null, (RDFNode)null)) {
            Resource propRes = prop.inModel(model).as(Resource.class);
            for (Statement s : prop.listProperties().toList()) {
                RDFNode object = s.getObject();
                if (s.getPredicate().getURI().equals(RDFS.domain.getURI())
                        || s.getPredicate().getURI().equals(RDFS.range.getURI())) {
                    object = model.createHasValueRestriction(null, model.getProperty(SKOS+"broaderTransitive"), s.getObject());
                }
                propRes.addProperty(s.getPredicate(), object);
            }
        }
    }

    private void convertSuperClasses(OntClass owlClass, Individual concept) {
        ExtendedIterator<OntClass> it = owlClass.listSuperClasses(true);
        while (it.hasNext()) {
            OntClass cls = it.next();
            if (cls.isIntersectionClass()) {
                handleIntersectionClass(owlClass, cls.asIntersectionClass(), concept);
            } else if (cls.isUnionClass()) {
                handleUnionClass(owlClass, cls.asUnionClass(), concept);
            } else if (cls.isRestriction()) {
                Restriction res = cls.asRestriction();
                if (res.isSomeValuesFromRestriction()) {
                    handleSomeValuesFrom(owlClass, res.asSomeValuesFromRestriction(),
                                         concept);
                } else if (res.isHasValueRestriction()) {
                    handleHasValue(owlClass, res.asHasValueRestriction(), concept);
                }
            } else if (cls.getURI() == null) {
                continue;
            } else {
                Individual i = getOrCreateConcept(cls.getURI());
                concept.addProperty(skosBroaderProperty, i);
            }
        }
        
    }

    private void handleHasValue(OntClass owlClass,
            HasValueRestriction res, Individual concept) {
        copyProperty(res.getOnProperty(),concept.getOntModel());
        concept.addProperty(res.getOnProperty(), res.getHasValue());
    }

    private void copyLabels(OntClass owlClass, Individual concept) {
        ExtendedIterator<RDFNode> it = owlClass.listLabels(null);
        while (it.hasNext()) {
            Literal lit = (Literal)it.next();
            concept.addLabel(lit);
            concept.addProperty(skosPrefLabelProperty,lit);
        }
    }

    private void copyComments(OntClass owlClass, Individual concept) {
        ExtendedIterator<RDFNode> it = owlClass.listComments(null);
        while (it.hasNext()) {
            Literal lit = (Literal) it.next();
            concept.addComment(lit);
            concept.addProperty(skosDefinitionProperty,lit);
        }
    }

    private void copyVersionInfo(OntClass owlClass, Individual concept) {
        ExtendedIterator<String> it = owlClass.listVersionInfo();
        while (it.hasNext()) {
            String ver = it.next();
            concept.addVersionInfo(ver);
        }
    }

    private Individual getOrCreateConcept(String uri) {
        Individual i = skosModel.getIndividual(uri);
        if (i == null) {
            i = skosConcept.createIndividual(uri);
        }
        return i;
    }

    public void write(File output) throws IOException {
        FileOutputStream os = null;
        try {
            os = new FileOutputStream(output);
            skosModel.write(os,"RDF/XML-ABBREV",defaultURI);
        } finally {
            if (os != null) {
                os.close();
            }
        }
    }
    
    public static void main(String[] args) throws IOException {
        if (args.length != 2) {
            printUsage();
            System.exit(1);
        }
        URL owl = null;
        try {
            owl = new URL(args[0]);
        } catch (MalformedURLException e1) {
            try {
                owl = new File(args[0]).toURI().toURL();
            } catch (MalformedURLException e) {
                System.err.println(args[0]+ " is not a valid URL or filename.");
                printUsage();
                System.exit(1);
            }
        }
        File output = new File(args[1]);
        OwlToSkosConverter converter = new OwlToSkosConverter();
        converter.loadOntology(owl.toString());
        converter.convert();
        converter.write(output);
    }
    
    private static void printUsage() {
        System.err.println("./owl2skos.sh <filename|URL> <outputfile.rdf>");
    }
    

    
}
